
#include <BFile>
#include <BXmlDocument>
#include "member_BXmlDocument.h"
#include "member_BXmlNode.h"

using namespace BWE;

#define member					(*(member_BXmlDocument*)_ptr)
#define member_allocate()		_ptr = new member_BXmlDocument(this)
#define member_release()		delete (member_BXmlDocument*)_ptr

BXmlDocument::BXmlDocument()
{
	member_allocate();
}
BXmlDocument::~BXmlDocument()
{
	for (int i = 0; i < member.nodes.size(); i++)
	{
		BXmlNode* node = member.nodes[i];
		xml_member(node)->document = 0;
	}
	member_release();
}

void BXmlDocument::setVersion(const BString& version)
{
	if (member.version != version)
	{
		member.version = version;
	}
}
const BString& BXmlDocument::version() const
{
	return member.version;
}

void BXmlDocument::setEncoding(const BString& encoding)
{
	if (member.encoding != encoding)
	{
		member.encoding = encoding;
	}
}
const BString& BXmlDocument::encoding() const
{
	return member.encoding;
}

bool BXmlDocument::setFormat(const BString& format)
{
	if (member.format.text != format)
	{
		member.format.fromString(format);
		return true;
	}
	return true;
}
const BString& BXmlDocument::format() const
{
	return member.format.text;
}

BXmlNode* BXmlDocument::load(const BString& fileName)
{
	BString xmltext;
	if (BFile::Load(fileName, xmltext))
		return read(xmltext);
	return 0;
}
bool BXmlDocument::save(const BString& fileName, const BXmlNode* root) const
{
	BString xmltext;
	if (write(xmltext, root))
		return BFile::Save(fileName, xmltext);
	return false;
}
bool BXmlDocument::save(const BString& fileName) const
{
	if (member.nodes.empty())
		return false;
	BFile file(fileName);
	if (file.open(IO_WriteText))
	{
		return write(file);
	}
	return false;
}

BXmlNode* BXmlDocument::read(const BStream& stream)
{
	BString xmltext;
	char code = 0;
	while (stream.read(code))
	{
		if (code == 0)
			break;
		xmltext.append(code);
	}
	return read(xmltext);
}
bool BXmlDocument::write(BStream& stream, const BXmlNode* root) const
{
	if (root)
	{
		BString xmltext;
		write(xmltext, root);
		return stream.write(xmltext.cstr(), xmltext.size());
	}
	return false;
}
bool BXmlDocument::write(BStream& stream) const
{
	BString xmltext;
	write(xmltext);
	return stream.write(xmltext.cstr(), xmltext.size());
}

BXmlNode* BXmlDocument::read(const BString& xmltext)
{
	member.doctype.clear();
	member.nodes.clear();
	member.lineNumber = 1;
	member.linePos = 0;
	BString name;
	BString text;
	char linebreak = 0;
	char code = 0;
	int pos = 0;
	while (pos < xmltext.size())
	{
		code = xmltext[pos++];
		if (code == '\n')
			linebreak = true;
		if (text.empty() && code != '<')
			continue;
		text.append(code);
		if (code == '-' && text.endWith("<!--"))
		{
			BXmlNode* node = new BXmlNode("!--");
			member.readComment(node, xmltext, pos);
			member.nodes.append(node);
			xml_member(node)->document = this;
			text.reset();
			linebreak = 0;
			continue;
		}
		if (code == '\"')
		{
			int end = xmltext.find('\"', pos);
			text.append(xmltext, pos, end);
			pos = end;
			if (end < xmltext.size())
			{
				text.append('\"');
				pos++;
			}
			continue;
		}
		if (code == '>')
		{
			if (text.beginWith("<!"))
			{
				int name_end = member.readTagName(text, name);
				BXmlNode* node = new BXmlNode(name);
				text.remove(0, name_end);
				text.remove(text.size() - 1, 1);
				name.lower();
				if (name == "!doctype")
				{
					member.readDoctype(node, text);
				}
				member.nodes.append(node);
				xml_member(node)->document = this;
				text.reset();
				linebreak = 0;
				continue;
			}
			if (text.beginWith("<?") && text.endWith("?>"))
			{
				int name_end = member.readTagName(text, name);
				BXmlNode* node = new BXmlNode(name);
				text.remove(0, name_end);
				text.remove(text.size() - 2, 2);
				member.readAttribs(node, text);
				name.lower();
				if (name == "<?xml")
				{
					member.version = node->attrib("version");
					member.encoding = node->attrib("encoding");
				}
				member.nodes.append(node);
				xml_member(node)->document = this;
				text.reset();
				linebreak = 0;
				continue;
			}
			if (text.first() == '<')
			{
				int name_end = member.readTagName(text, name);
				if (name == "html")
					member.doctype = name;
				BXmlNode* node = new BXmlNode(name);
				xml_member(node)->format.head_prev = linebreak;
				linebreak = 0;
				if (text.endWith("/>"))
				{
					text.remove(0, name_end);
					text.remove(text.size() - 2);
					member.readAttribs(node, text);
				}
				else
				{
					text.remove(0, name_end);
					text.remove(text.size() - 1);
					member.readAttribs(node, text);
					member.readNode(node, xmltext, pos);
				}
				member.nodes.append(node);
				xml_member(node)->document = this;
				text.reset();
				linebreak = 0;
				continue;
			}
			break;
		}
	}
	for (int i = 0; i < member.nodes.size(); i++)
	{
		BXmlNode* node = member.nodes[i];
		member.makeFormat(node);
	}
	return member.nodes.first();
}
bool BXmlDocument::write(BString& xmltext, const BXmlNode* root) const
{
	if (member.version.size() || member.encoding.size())
	{
		xmltext << "<?xml";
		if (member.version.size())
			xmltext << " version=\"" << member.version << '\"';
		if (member.encoding.size())
			xmltext << " encoding=\"" << member.encoding << '\"';
		xmltext << "?>\n";
	}
	member.doctype = root->name();
	member.doctype.lower();
	member.writeNode(root, xmltext, 0);
	return true;
}
bool BXmlDocument::write(BString& xmltext) const
{
	if (member.nodes.empty())
		return false;
	if (member.nodes.size())
	{
		for (int i = 0; i < member.nodes.size(); i++)
		{
			BXmlNode* node = member.nodes[i];
			member.doctype = node->name();
			member.doctype.lower();
			member.writeNode(member.nodes[i], xmltext, 0);
		}
		return true;
	}
	return false;
}

void BXmlDocument::setNode(BXmlNode* node)
{
	BHolder<BXmlNode> holder(node);
	member.nodes.clear();
	member.nodes.append(node);
}
BXmlNode* BXmlDocument::node()
{
	return member.nodes.first();
}

void BXmlDocument::addNode(BXmlNode* node)
{
	if (node && xml_member(node)->document != this)
	{
		BXmlNodeHolder holder(node);
		if (xml_member(node)->document)
			xml_member(node)->document->removeNode(node);
		member.nodes.append(node);
		xml_member(node)->document = this;
	}
}
BXmlNode* BXmlDocument::addNode(const BString& name)
{
	BXmlNodeHolder node = new BXmlNode(name);
	addNode(node);
	return node;
}

bool BXmlDocument::removeNode(BXmlNode* node)
{
	if (node && xml_member(node)->document == this)
	{
		xml_member(node)->document = 0;
		return member.nodes.remove(node);
	}
	return false;
}
bool BXmlDocument::removeNode(const BString& name)
{
	if (BXmlNode* node = this->node(name))
	{
		xml_member(node)->document = 0;
		return member.nodes.remove(node);
	}
	return false;
}
void BXmlDocument::clearNodes()
{
	for (int i = 0; i < member.nodes.size(); i++)
	{
		BXmlNode* node = member.nodes[i];
		xml_member(node)->document = 0;
	}
	member.nodes.clear();
}

int BXmlDocument::nodeCount() const
{
	return member.nodes.size();
}
BXmlNode* BXmlDocument::node(int index)
{
	return member.nodes(index);
}
const BXmlNode* BXmlDocument::node(int index) const
{
	return member.nodes(index);
}

BXmlNode* BXmlDocument::node(const char* name)
{
	if (!name)
		return 0;
	for (int i = 0; i < member.nodes.size(); i++)
	{
		if (member.nodes[i]->name() == name)
			return member.nodes[i];
	}
	return 0;
}
const BXmlNode* BXmlDocument::node(const char* name) const
{
	if (!name)
		return 0;
	for (int i = 0; i < member.nodes.size(); i++)
	{
		if (member.nodes[i]->name() == name)
			return member.nodes[i];
	}
	return 0;
}

BXmlNode* BXmlDocument::node(const BString& name)
{
	if (name.empty())
		return 0;
	for (int i = 0; i < member.nodes.size(); i++)
	{
		if (member.nodes[i]->name() == name)
			return member.nodes[i];
	}
	return 0;
}
const BXmlNode* BXmlDocument::node(const BString& name) const
{
	if (name.empty())
		return 0;
	for (int i = 0; i < member.nodes.size(); i++)
	{
		if (member.nodes[i]->name() == name)
			return member.nodes[i];
	}
	return 0;
}

BXmlNode* BXmlDocument::create(const BString& path)
{
	int end = path.find('/');
	if (end == path.size())
	{
		BXmlNode* node = this->node(path);
		if (node == 0)
			node = this->addNode(path);
		return node;
	}
	BString text;
	path.substr(text, 0, end);
	BXmlNode* node = this->node(text);
	if (node == 0)
	{
		node = this->addNode(text);
	}
	int pos = end + 1;
	while (node && pos < path.size())
	{
		end = path.find('/', pos);
		path.substr(text, pos, end);
		BXmlNode* child = node->child(text);
		if (child == 0)
			child = node->addChild(text);
		node = child;
		pos = end + 1;
	}
	return node;
}

BXmlNode* BXmlDocument::locate(const BString& path)
{
	int end = path.find('/');
	if (end == path.size())
		return this->node(path);
	BString text;
	path.substr(text, 0, end);
	BXmlNode* node = this->node(text);
	int pos = end + 1;
	while (node && pos < path.size())
	{
		end = path.find('/', pos);
		path.substr(text, pos, end);
		node = node->child(text);
		pos = end + 1;
	}
	return node;
}
const BXmlNode* BXmlDocument::locate(const BString& path) const
{
	int end = path.find('/');
	if (end == path.size())
		return this->node(path);
	BString text;
	path.substr(text, 0, end);
	const BXmlNode* node = this->node(text);
	int pos = end + 1;
	while (node && pos < path.size())
	{
		end = path.find('/', pos);
		path.substr(text, pos, end);
		node = node->child(text);
		pos = end + 1;
	}
	return node;
}

